import { FoxglovePrimitive, FoxgloveSchema } from "./types";

function primitiveToIdl(type: Exclude<FoxglovePrimitive, "time" | "duration">) {
  switch (type) {
    case "bytes":
      return "sequence<uint8>";
    case "string":
      return "string";
    case "boolean":
      return "boolean";
    case "float64":
      return "double";
    case "uint32":
      return "uint32";
  }
}

export const TIME_IDL = `\
module foxglove {

struct Time {
  uint32 sec;
  uint32 nsec;
};

};
`;

export const DURATION_IDL = `\
module foxglove {

struct Duration {
  int32 sec;
  uint32 nsec;
};

};
`;

export function generateOmgIdl(schema: FoxgloveSchema): string {
  const imports = new Set<string>();

  let definition: string;
  switch (schema.type) {
    case "enum": {
      const fields = schema.values.map(({ name, value, description }, index) => {
        const separator = index === schema.values.length - 1 ? "" : ",";
        if (value !== index) {
          throw new Error(
            `Enum value ${schema.name}.${name} at index ${index} has value ${value}; index and value must match for OMG IDL`,
          );
        }
        if (description != undefined) {
          return `// ${description}\n  // Value: ${value}\n  ${name}${separator}`;
        } else {
          return `// Value: ${value}\n  ${name}${separator}`;
        }
      });
      definition = `// ${schema.description}\nenum ${schema.name} {\n  ${fields.join(
        "\n\n  ",
      )}\n};`;
      break;
    }

    case "message": {
      const fields = schema.fields.map((field) => {
        let fieldType: string;
        switch (field.type.type) {
          case "enum":
            fieldType = field.type.enum.name;
            imports.add(field.type.enum.name);
            break;
          case "nested":
            fieldType = field.type.schema.name;
            imports.add(field.type.schema.name);
            break;
          case "primitive":
            if (field.type.name === "time") {
              fieldType = "Time";
              imports.add("Time");
            } else if (field.type.name === "duration") {
              fieldType = "Duration";
              imports.add("Duration");
            } else {
              fieldType = primitiveToIdl(field.type.name);
            }
            break;
        }
        let arraySize = "";
        if (typeof field.array === "number") {
          arraySize = `[${field.array}]`;
        } else if (field.array != undefined) {
          fieldType = `sequence<${fieldType}>`;
        }
        const descriptionLines = field.description.trim().split("\n");
        const comment = descriptionLines.map((line) => `// ${line}`).join("\n  ");

        let defaultAnnotation = "";
        if (typeof field.defaultValue === "string") {
          defaultAnnotation = `@default(${JSON.stringify(field.defaultValue)})\n  `;
        } else if (typeof field.defaultValue === "number") {
          // For floating-point fields with integer default values, ensure we output a number with
          // at least one decimal place so it is interpreted as an IDL floating-point literal
          if (
            field.type.type === "primitive" &&
            field.type.name === "float64" &&
            Number.isInteger(field.defaultValue)
          ) {
            defaultAnnotation = `@default(${field.defaultValue.toFixed(1)})\n  `;
          } else {
            defaultAnnotation = `@default(${field.defaultValue})\n  `;
          }
        } else if (typeof field.defaultValue === "boolean") {
          defaultAnnotation = `@default(${field.defaultValue ? "TRUE" : "FALSE"})\n  `;
        }

        return `${comment}\n  ${defaultAnnotation}${fieldType} ${field.name}${arraySize};`;
      });

      definition = `// ${schema.description}\nstruct ${schema.name} {\n  ${fields.join(
        "\n\n  ",
      )}\n};`;
      break;
    }
  }

  const outputSections = [
    `// Generated by https://github.com/foxglove/foxglove-sdk`,

    Array.from(imports)
      .sort()
      .map((name) => `#include "foxglove/${name}.idl"`)
      .join("\n"),

    "module foxglove {",
    definition,
    "};",
  ].filter(Boolean);

  return outputSections.join("\n\n") + "\n";
}
